package com.seu.spring.advance.ioc.factory;

import com.seu.spring.advance.ioc.BeanDefinition;
import com.seu.spring.advance.ioc.BeanReference;
import com.seu.spring.advance.ioc.PropertyValue;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * 可实现自动装配的BeanFactory
 *
 * @author liangfeihu
 * @since 2018/12/29 11:06
 */
public class AutowireCapableBeanFactory extends AbstractBeanFactory {

    /**
     * 通过反射自动装配bean的所有属性
     */
    @Override
    protected void applyPropertyValues(Object bean, BeanDefinition beanDefinition) throws Exception {
        if (bean instanceof BeanFactoryAware) {
            ((BeanFactoryAware) bean).setBeanFactory(this);
        }
        for (PropertyValue propertyValue : beanDefinition.getPropertyValues().getPropertyValues()) {
            Object value = propertyValue.getValue();
            // 如果属性是ref而不是value类型就先实例化那个ref的bean，然后装载到这个value里
            if (value instanceof BeanReference) {
                BeanReference beanReference = (BeanReference) value;
                // 先实例化ref的bean再装配进去
                value = getBean(beanReference.getName());
            }

            try {
                /**
                 * 例如：<bean id="dog" class="com.*.*.entity.Dog">
                 *           <property name="Id" value="101"></property>
                 *       </bean>
                 * 先获取dog对象的setId方法，然后通过反射调用该方法将value设置进去
                 * getDeclaredMethod方法的第一个参数是方法名，第二个参数是该方法的参数列表
                 */
                Method declaredMethod = bean.getClass().getDeclaredMethod(
                        // 拼接方法名
                        "set" + propertyValue.getName().substring(0, 1).toUpperCase()
                                + propertyValue.getName().substring(1),
                        value.getClass());

                declaredMethod.setAccessible(true);
                declaredMethod.invoke(bean, value);
            } catch (NoSuchMethodException e) {
                // 如果该bean没有setXXX的类似方法，就直接将value设置到相应的属性域内
                Field declaredField = bean.getClass().getDeclaredField(propertyValue.getName());
                declaredField.setAccessible(true);
                declaredField.set(bean, value);
            }
        }
    }

}
